

import config
import logging
from aiogram.dispatcher import filters
from aiogram import Bot, Dispatcher, executor, types
from aiogram.types import InputTextMessageContent, InlineQueryResultArticle, InlineQueryResultCachedSticker, InputMediaAnimation, InlineQueryResultCachedMpeg4Gif
from aiogram.utils.exceptions import MessageNotModified
from contextlib import suppress
import hashlib
import markups as nav
from payment import PaymentSystem
from pprint import pprint
from game import Game
import asyncio
from database import Database
import pprint
import json

from loguru import logger
from sys import stderr


def make_filter(name):
    def filter(record):
        return record["extra"].get("name") == name
    return filter


logger.remove()
logger.add("logs/messages.log",
           filter=make_filter("messages"), rotation="100 MB")
messages_logger = logger.bind(name="messages")

logger.add("logs/main.log", filter=make_filter("logs"), rotation="100 MB")
logger.add(stderr, filter=make_filter("logs"), format="<white>{time:HH:mm:ss}</white>"
           " | <level>{level: <8}</level>"
           " | <cyan>{line}</cyan>"
           " - <white>{message}</white>")


logger = logger.bind(name='logs')

# print(PaymentSystem.generate_wallet())

MIN_WITHDRAW = 25
MIN_DEPOSIT = 25

db = Database()
ps = PaymentSystem(config.TRON_PRIVATE, logger)


TOKEN = config.API_BOT

logging.basicConfig(level=logging.INFO)

bot = Bot(token=TOKEN)
dp = Dispatcher(bot)

tron_wallet_regexp = r'T[A-Za-z1-9]{33} [\d]+'


async def warn_all_admins(text):
    admins = db.get_admins()
    for admin in admins:
        try:
            await bot.send_message(int(admin), text, parse_mode='markdown')
        except:
            pass


async def broadcast_message(text):
    logger.debug('Броадкаст с текстом: '+text)
    try:
        await asyncio.gather(*[bot.send_message(int(user), text) for user in db.data['users'].keys()])
    except:
        pass


@dp.message_handler(commands=['broadcast'])
async def broadcast_admin(message: types.Message):
    if db.is_admin(message.from_user.id):
        arguments = message.get_args()
        await broadcast_message(arguments)


@dp.message_handler(commands=['dep'])
async def deposit_cmd(message: types.Message):
    if message.chat.type == 'private':
        arguments = message.get_args()
        amount = 0
        try:
            amount = int(arguments)
        except ValueError:
            await bot.send_message(message.from_user.id, 'Неверное использование команды', parse_mode='markdown')
            return
        if amount < MIN_DEPOSIT:
            await bot.send_message(message.from_user.id, f'Минимальная сумма пополнения {MIN_DEPOSIT} USDT', parse_mode='markdown')
            return
        amount, extra = ps.try_create_invoice(message.from_id, amount)
        if amount is None:
            await bot.send_message(message.from_user.id, 'Пополнение на данную сумму сейчас не доступно, выберите другую или свяжитесь с администраторами', parse_mode='markdown')
            return
        if extra != 0:
            total_str = f"{amount}.{str(extra).zfill(ps.DECIMALS)}"
        else:
            total_str = f"{amount}"
        text = f'Пополните адрес: `{ps.get_address()}`\nНа сумму: `{total_str}` USDT\nСрок оплаты: {round(ps.DURATION/1000/60)} минут\n\nПри нажатии значение копируется'
        await bot.send_message(message.from_user.id, text, parse_mode='markdown', reply_markup=nav.depositMenu)


@dp.message_handler(filters.Regexp(tron_wallet_regexp))
async def withdraw_to_address(message: types.Message):
    if message.chat.type == 'private':
        arguments = message.text.split(' ')
        wallet = arguments[0]
        amount = int(arguments[1])
        balance = db.get_balance(message.from_id)
        balance_str = ('%f' % balance).rstrip('0').rstrip('.')
        if amount >= MIN_WITHDRAW:
            if amount <= balance:
                await warn_all_admins(f'Пользователь `{message.from_user.full_name}` с id=`{message.from_id}` запросил вывод `{amount}` USDT на адрес {wallet}')
                logger.debug(
                    f'Пользователь {message.from_user.id} хочет вывести свои шекели и у него достаточно средств {balance_str}')
                await bot.send_message(message.from_user.id, f"Был подан запрос на вывод {amount} USDT на кошелек `{wallet}`", parse_mode='markdown')
                db.decrease_balance(message.from_id, amount)
                txn_id = await ps.send_usdt_royalty(amount, wallet)
                await warn_all_admins(f'Транзакция:\nhttps://tronscan.org/#/transaction/{txn_id}')
                if txn_id == 0:
                    await bot.send_message(message.from_user.id, f"Обратитесь к администрации, произошла ошибка")
                    return
                await bot.send_message(message.from_user.id, f"Транзакция:\nhttps://tronscan.org/#/transaction/{txn_id}", parse_mode='markdown')
                await bot.send_message(message.from_user.id, f"Бот для игры в кости 🎲\nВаш текущий баланс: {db.get_balance(message.from_id)} USDT", reply_markup=nav.mainMenu)
            else:
                logger.debug(
                    f'Пользователь {message.from_user.id} хочет вывести свои шекели, но не хватает бабок')
                await bot.send_message(message.from_user.id, f"Ваш баланс меньше указанной суммы USDT, вывод не доступен", parse_mode='markdown')
        else:
            logger.debug(
                f'Пользователь {message.from_user.id} хочет вывести свои шекели, но слишком мало')
            await bot.send_message(message.from_user.id, f"Сумма меньше {MIN_WITHDRAW} USDT, вывод не доступен", parse_mode='markdown')


@dp.message_handler(commands=['id'])
async def id(message: types.Message):
    if db.is_admin(message.from_user.id):
        try:
            await message.reply(f"Name: `{message.reply_to_message.from_user.first_name}`\nId: `{message.reply_to_message.from_user.id}`", parse_mode='markdown')
        except:
            pass


@dp.message_handler(commands=['total'])
async def total(message: types.Message):
    if db.is_admin(message.from_user.id):
        try:
            await bot.send_message(message.from_id, f"Пользователей в боте: {len(db.data['users'])}")
        except:
            pass


@dp.message_handler(commands=['admin'])
async def admin(message: types.Message):
    if message.chat.type == 'private':
        if db.is_admin(message.from_id):
            await bot.send_message(message.from_id, "Для установки баланса пользователя по его айди:\n/setbalance id balance\n\nПолучить баланс бота:\n/botbalance\n\nПолучить баланс пользоватля:\n/getbalance id\n\nПополнить свой баланс:\n/addbalance amount\n\nПолучить базу данных:\n/getdatabase\n\nОтправить сообщение всем пользователям:\n/broadcast сообщение\n\nУзнать айди пользователя:\nнаписать /id в ответ на него сообщение в чате, где есть бот\n\nУзнать количество пользователей в боте:\n/total")


@dp.message_handler(commands=['getdatabase'])
async def getdatabase(message: types.Message):
    if message.chat.type == 'private':
        if db.is_admin(message.from_id):
            info = json.dumps(db.data, indent=5)
            if len(info) > 4090:
                for x in range(0, len(info), 4090):
                    await bot.send_message(message.from_id,  '`'+info[x:x+4090]+'`', parse_mode='markdown')
            else:
                await bot.send_message(message.from_id,  '`'+info+'`', parse_mode='markdown')


@dp.message_handler(commands=['getbalance'])
async def getbalance(message: types.Message):
    if message.chat.type == 'private':
        if db.is_admin(message.from_id):
            try:
                arguments = message.get_args()
                result = db.get_balance(arguments)
                await bot.send_message(message.from_id, f'{result} USDT')
            except Exception as err:
                await bot.send_message(message.from_id, err)


@dp.message_handler(commands=['addbalance'])
async def addbalance(message: types.Message):
    if message.chat.type == 'private':
        if db.is_admin(message.from_id):
            try:
                arguments = message.get_args()
                db.add_balance(message.from_id, arguments)
                await bot.send_message(message.from_id, f'Успешно выполнено')
            except Exception as err:
                await bot.send_message(message.from_id, err)


@dp.message_handler(commands=['setbalance'])
async def setbalance(message: types.Message):
    if message.chat.type == 'private':
        if db.is_admin(message.from_id):
            try:
                arguments = message.get_args().split(' ')
                # print(arguments)
                db.set_balance(arguments[0], arguments[1])
                await bot.send_message(message.from_id, "Успешно выполнено")
            except Exception as err:
                await bot.send_message(message.from_id, err)


@ dp.message_handler(commands=['botbalance'])
async def botbalance(message: types.Message):
    if message.chat.type == 'private':
        if db.is_admin(message.from_id):
            response = await ps.get_bank()
            await bot.send_message(message.from_id, response)


@ dp.message_handler(commands=['start'])
async def start(message: types.Message):
    if message.chat.type == 'private':
        db.try_create_user(message.from_user.first_name, message.from_id, 0)
        balance = ('%f' % db.get_balance(
            message.from_id)).rstrip('0').rstrip('.')
        # check create user if not
        await bot.send_message(message.from_user.id, f"Бот для игры в кости 🎲\nВаш текущий баланс: {balance} USDT", reply_markup=nav.mainMenu)


@ dp.callback_query_handler(text="btnRefresh")
async def refresh(query: types.CallbackQuery):
    db.try_create_user(query.from_user.first_name, query.from_user.id, 0)
    balance = ('%f' % db.get_balance(
        query.from_user.id)).rstrip('0').rstrip('.')
    with suppress(MessageNotModified):
        await query.message.edit_text(f"Бот для игры в кости 🎲\nВаш текущий баланс: {balance} USDT ", reply_markup=nav.mainMenu)


@ dp.callback_query_handler(text="btnAdmins")
async def contacts(query: types.CallbackQuery):
    await bot.send_message(query.from_user.id, """Администраторы:""")


@ dp.callback_query_handler(text="btnDeposit")
async def deposit(query: types.CallbackQuery):
    with suppress(MessageNotModified):
        await query.message.edit_text(f"""Пополнение:

Для пополнения баланса через trc20 USDT:
1) пропишите команду /dep сумма
2) получите адрес и сумму для оплаты
3) отправьте точную сумму из 2 шага, которую выслал вам бот
4) отправьте транзакцию и нажмите кнопку "Проверить оплату"

**Пополняйте точный баланс, который прислал вам бот, иначе деньги не будут зачислены!**

Если вы создаете более 1 запроса на пополнение, то активным будет только последний, другой затирается

Минимальная сумма пополнения равна {MIN_DEPOSIT} USDT

Пополнение в другой крипте через администраторов""",
                                      parse_mode='markdown', reply_markup=nav.mainMenu)


@ dp.callback_query_handler(text="btnWithdraw")
async def withdraw(query: types.CallbackQuery):
    db.try_create_user(query.from_user.first_name, query.from_user.id, 0)
    balance = ('%f' % db.get_balance(
        query.from_user.id)).rstrip('0').rstrip('.')
    await query.message.edit_text(f"Ваш текущий баланс: {balance} USDT\nДля вывода укажите свой кошелек и через пробел сумму вывода в trc20  USDT, например:\nTSssZAw3AHbETtSyxErZzDi6K5sjeo8HUS 250\nМинимальная сумма списания {MIN_WITHDRAW} USDT\nКомиссия сервиса {ps.royalty}%", reply_markup=nav.mainMenu)


@ dp.callback_query_handler(text="btnCheckDeposit")
async def check_deposit(query: types.CallbackQuery):
    result = await ps.check_invoice_status(query.from_user.id)
    if result is None or result == 'other' or result == 'weird':
        await bot.send_message(query.from_user.id, 'Ваш инвойс был исключен из системы или не найден')
        logger.debug(
            f'Пользователю {query.from_user.id} - {"Ваш инвойс был исключен из системы или не найден"}')
        return
    if result == 'exceeded':
        await bot.send_message(query.from_user.id, 'Ваш инвойс или просрочен, или исполнен')
        logger.debug(
            f'Пользователю {query.from_user.id} - {"Ваш инвойс или просрочен, или исполнен"}')
        return
    if isinstance(result, int):
        if result == 0:
            await bot.send_message(query.from_user.id, 'Ваша транзакция еще не пришла')
            logger.debug(
                f'Пользователю {query.from_user.id} - {"Ваша транзакция еще не пришла"}')
        else:
            db.add_balance(query.from_user.id, result)
            await bot.send_message(query.from_user.id, f'Ваш баланс пополнен на {result} USDT')
            logger.debug(
                f'Пользователю {query.from_user.id} - Ваш баланс пополнен на {result} USDT')
            await warn_all_admins(f'Пользователю {query.from_user.id} {query.from_user.full_name} баланс пополнен на {result} USDT')
            balance = db.get_balance(query.from_user.id)
            await query.message.edit_text(f"Бот для игры в кости 🎲\nВаш текущий баланс: {balance} USDT", reply_markup=nav.mainMenu)
    else:
        await bot.send_message(query.from_user.id, 'Неведомая ошибка пополнения, обратитесь к администрации')


@ dp.callback_query_handler(text="btnCancelDeposit")
async def cancel_deposit(query: types.CallbackQuery):
    balance = ('%f' % db.get_balance(
        query.from_user.id)).rstrip('0').rstrip('.')
    await query.message.edit_text(f"Бот для игры в кости 🎲\nВаш текущий баланс: {balance} USDT", reply_markup=nav.mainMenu)


@ dp.inline_handler()
async def create_game_message(inline_query: types.InlineQuery):
    print(inline_query)
    cost = 5
    text = f'Игра в кости 🎲\nCтавка: {cost} USDT\n\nЧтобы создать игру нажмите кнопку'

    item = InlineQueryResultArticle(
        id=hashlib.md5(text.encode()).hexdigest(),
        title='Создать ставку на бросок 🎲',
        input_message_content=InputTextMessageContent(text),
        reply_markup=nav.playMenu
    )

    print(inline_query.id)
    await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)


@ dp.callback_query_handler(text="btnSettingPrice")
async def prepare_game(query: types.ChosenInlineResult):
    Game.appendNewGame(query.inline_message_id, 5,
                       query.from_user.id, query.from_user.first_name)
    await bot.edit_message_text('Текущая ставка: 5 USDT', inline_message_id=query.inline_message_id, reply_markup=nav.playMenuSettingPrice)


@ dp.callback_query_handler(text=["btnDivTen", 'btnMinusFive', 'btnPlusFive', 'btnMulTen'])
async def change_price(query: types.CallbackQuery):
    try:
        game = Game.getGameByInline(query.inline_message_id)
        if game.isCreator(query.from_user.id) and game.started == False:
            cost = Game.getGameByInline(
                query.inline_message_id).changeCost(query['data'])
            await bot.edit_message_text(f'Текущая ставка: {cost} USDT', inline_message_id=query.inline_message_id, reply_markup=nav.playMenuSettingPrice)
        else:
            await query.answer('Ты не создатель текущей игры')
    except:
        pass


@ dp.callback_query_handler(text=['btnContinue'])
async def start_waiting_opponent(query: types.CallbackQuery):
    try:
        game = Game.getGameByInline(query.inline_message_id)
        if game.isCreator(query.from_user.id) and game.started == False:
            await bot.edit_message_text(f'Игрок {query.from_user.first_name} ожидает оппонента.\nСтавка: {game.cost} USDT', inline_message_id=query.inline_message_id, reply_markup=nav.playMenuWaiting)
        else:
            await query.answer('Ты не создатель текущей игры')
    except:
        pass


@ dp.callback_query_handler(text=['btnConnectToGame'])
async def start_game(query: types.CallbackQuery):
    try:
        game = Game.getGameByInline(query.inline_message_id)
        if game.isCreator(query.from_user.id):
            await query.answer('Вы не можете начать игру с самим собой, ожидайте оппонента')
        else:
            if game.finished == False:  # !!!!!!!!!!!!!!!!!!!!!!!!
                game.setOpponent(query.from_user.id,
                                 query.from_user.first_name)
                game.setStarted(True)
                # проверить хватает ли у них бабок на игру
                u1_balance = db.get_balance(game.creator)
                u2_balance = db.get_balance(game.opponent)
                if (u1_balance < game.cost):
                    await bot.edit_message_text(f'У пользователя {game.creator_name} не хватает средств', inline_message_id=query.inline_message_id)
                    return
                if (u2_balance < game.cost):
                    await bot.edit_message_text(f'У пользователя {game.opponent_name} не хватает средств', inline_message_id=query.inline_message_id)
                    return

                db.decrease_balance(game.creator, game.cost)
                db.decrease_balance(game.opponent, game.cost)
                # снизить их балансы

                await bot.edit_message_text(f'Игра начинается...', inline_message_id=query.inline_message_id)
                await asyncio.sleep(0.3)
                await bot.edit_message_text(f'Игра начинается..', inline_message_id=query.inline_message_id)
                await asyncio.sleep(0.3)
                await bot.edit_message_text(f'Игра начинается.', inline_message_id=query.inline_message_id)
                await asyncio.sleep(0.3)
                await bot.edit_message_text(f'Игра начинается', inline_message_id=query.inline_message_id)
                await asyncio.sleep(1)
                result = Game.getGameByInline(
                    query.inline_message_id).pickWinner()
                emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣']
                r1 = ''.join([emojis[i-1] for i in result[0]])
                r1int = sum(result[0])
                r2 = ''.join([emojis[i-1] for i in result[1]])
                r2int = sum(result[1])
                winner_name = ''
                if r1int > r2int:
                    winner_name = 'Победил '+game.creator_name
                    db.add_balance(game.creator, game.cost*2)
                    # логика виннера
                elif r1int < r2int:
                    winner_name = 'Победил '+game.opponent_name
                    db.add_balance(game.opponent, game.cost*2)
                    # логика виннера
                else:
                    winner_name = 'Ничья'
                    db.add_balance(game.creator, game.cost)
                    db.add_balance(game.opponent, game.cost)
                text = f'Игрок {game.creator_name}:\n{r1}\n\nИгрок {game.opponent_name}:\n{r2}\n\n'+winner_name

                await bot.edit_message_text(text, inline_message_id=query.inline_message_id)

    except Exception as e:
        print(e)
        pass


@dp.message_handler()
async def messages(message: types.Message):
    messages_logger.info(message)

if __name__ == "__main__":
    executor.start_polling(dp, skip_updates=True)
